Skip to content

Conversation

@nikgraf
Copy link
Collaborator

@nikgraf nikgraf commented Feb 17, 2025

Added createProperty, createType, createEntity

publish and createSpace are also in there, publish needs more work and therefor I haven't documented it yet

@nikgraf nikgraf requested a review from baiirun February 17, 2025 17:25
@nikgraf nikgraf self-assigned this Feb 17, 2025
@changeset-bot
Copy link

changeset-bot bot commented Feb 17, 2025

🦋 Changeset detected

Latest commit: 3996cb5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@graphprotocol/grc-20 Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@nikgraf nikgraf force-pushed the ng/utility-functions branch from 5d937d1 to 06d1784 Compare February 17, 2025 17:30
index.ts Outdated
*
* @since 0.5.0
*/
export * as Graph from './src/graph/index.js';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yeah not sure what we call this namespace. Graph seems fine I suppose.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, no better idea here so far

Comment on lines 31 to 44
let attributeValueType: 'TEXT' | 'CHECKBOX' | 'NUMBER' | 'URL' | 'POINT';
switch (typeof value) {
case 'string':
attributeValueType = 'TEXT';
break;
case 'boolean':
attributeValueType = 'CHECKBOX';
break;
case 'number':
attributeValueType = 'NUMBER';
break;
default:
throw new Error('Not implemented value type');
}
Copy link
Contributor

@baiirun baiirun Feb 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So a couple points

  • We probably want to make sure we support creating relations before we introduce something like this since creating relations is very common
  • I think we should use the attribute id's value type as the source of truth for creating an entity rather than the JS value's primitive type. This would require fetching the data for each attribute and comparing it. But then we need to think about spaces and this starts to get more complex and verge into hypergraph territory. What if the value they use is different than the attribute's value type? Developers should be able to publish whatever triples they want even if it's against the schema. WDYT?

Copy link
Collaborator Author

@nikgraf nikgraf Feb 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • makes sense @ relations, will add them
  • how about a different mapping param e.g.
{
  "name": {
    id: "LuBWqZAu6pz54eiJS5mLv8",
    valueType: "TEXT"
  },
  age: {
    id: "someOtherId",
    valueType: "NUMBER"
  }
}

But then should be decode the input an validate it matches the valueType? Feel odd if we allow to set the value type to number and then it would accept a string.

Copy link
Contributor

@baiirun baiirun Feb 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think this is where it's tricky since the op itself should be the source of truth for what the value type is, and I don't think at this low level we want to force validation or anything since developers should be able to write whatever they want to the KG.

If we wanted to enforce validation then it should be a higher-level abstraction that lives in a higher-level package like hypergraph.

@nikgraf nikgraf force-pushed the ng/utility-functions branch 2 times, most recently from fe8cc18 to bb35e3c Compare February 18, 2025 17:08
name: string;
};

export const createRelationType = async ({ from, to, name }: Params) => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@baiirun the from and to are basically unused atm. I don't know if we even need them or and what triples should be set.

If I look at this type it doesn't seem to define from and to https://www.geobrowser.io/space/25omwWh6HYgeRQKCaSpVpa/U1uCAzXsRSTP4vFwo1JwJG

Only at the actual relation they seem to be defined: https://www.geobrowser.io/space/6tfhqywXtteatMeGUtd5EB/VbYuNDM2S9LKyij73Aj5CA

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is createRelationType meant to do?

Copy link

@yanivtal yanivtal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some feedback on the API


describe('Graph', () => {
it('creates multiple types, relation types, entities, relations', async () => {
const operations: Array<Op> = [];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like we've standardized on the word ops, so it probably makes sense to stick to that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will change it to ops. Personally I don't like the naming op since I think it's a not obvious abbreviation to operation.

const {
id: personTypeId,
ops: createPersonTypeOps,
mapping: personMapping,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels very heavy weight to pass this mapping around.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will create other proposals. I tried this approach since having one big mapping file will cause other developer experience issues.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be an alternative version where you have function factory. Personally I would love something simpler. If you like it we can go with this one now. Since I learn more and more about GRC-20 specifics every day an alternative design might come out of this.

// initially defined mapping for an event
const mapping = {
  event: {
    types: ['AaGd6UMXfNtL6U6Xx7K8Cv'], // Event type
    attributes: {
      name: {
        id: NAME_ATTRIBUTE,
        type: 'TEXT',
      },
    },
    relations: {
      attendees: {
        relationTypeId: 'AaGd6UMXfNtL6U6Xx7K8Cv',
      },
    },
  },
};

// create a new attribute age
const { ops, id: ageAttributeId } = createAttributeEntity({
  type: 'NUMBER',
  name: 'Age',
});

// create a new attribute metFirst
const { ops, id: metFirstAttributeId } = createAttributeEntity({
  type: 'TIME',
  name: 'Met First',
});

// create a new type person
const { ops, mapping: personMapping } = createType({
  attributes: {
    name: {
      id: NAME_ATTRIBUTE,
      type: 'TEXT',
    },
    age: {
      id: ageAttributeId,
      type: 'NUMBER',
    },
  },
  relations: {
    friend: {
      relationTypeId: 'AaGd6UMXfNtL6U6Xx7K8Cv',
      attributes: {
        metFirst: {
          id: metFirstAttributeId,
          type: 'TIME',
        },
      },
    },
  },
});

// add the new type to the mapping under the property person
mapping.person = personMapping;

// create the createEntity function
const createEntity = createEntityFactory(mapping);

// create an entity
const { ops: personOps, mapping: personMapping } = createEntity({
  mappingKey: 'person',
  attributes: {
    name: 'John Doe',
  },
  relations: {
    friend: [
      {
        id: 'ABCD', // other person ID
        attributes: {
          metFirst: 'Jane Doe',
        },
      },
    ],
  },
});

Copy link

@yanivtal yanivtal Feb 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we're over complicating things. Do we need the mappings? We could keep the API pretty close to the metal, just to create some convenience. What if it was just:

  const { id: ID, ops: personOps } = createEntity({
    triples: {
      [NAME_PROPERTY]: {
        value: 'John Doe',
        type: TEXT,
      },
    },
    relations: [
      {
        type: TYPES_PROPERTY,
        to: PERSON_TYPE,
      },
      {
        type: WORKS_AT_PROPERTY,
        to: companyId,
        triples: {
          [START_TIME_PROPERTY]: {
            value: startTime,
            type: TIME,
            format: 'MMMM do, yyyy'
        },
      }
    ]
  })

We could fold the types into the createEntity API but I'm not sure how much value it adds. The fact is this GRC-20 lib is still meant to be a fairly low level API. We want to have conveniences so people aren't dealing with encoding, decoding, etc. But I actually think it's fine for this API to expose the nuances of GRC-20. I don't think the goal should be that people don't need to understand the GRC-20 specifics. I think it's just to make working with it more ergonomic. I think this achieves that. Not perfectly ergonomic but not bad - and not adding magic and indirection.

Copy link

@yanivtal yanivtal Feb 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I nice thing about using arrays for relations is that in shows that these are ordered. We should generate indexes for the relations. Triples aren't ordered, so it makes sense to have that in an object.

One potential issue to solve is right now we don't have a way to get the created relation IDs back from the function.

Copy link

@yanivtal yanivtal Feb 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createRelation(
  {
    type: WORKS_AT_PROPERTY,
    from: personId,
    to: companyId,
    triples: {
      [START_TIME_PROPERTY]: {
        value: startTime,
        type: TIME,
      }
   }
)

This would add Types: Relation and then set the given type here to the relationType.

ops: createRestaurantTypeOps,
mapping: restaurantMapping,
} = await createType({
attributes: {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we show an example of creating a Type that has attributes and relations?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

totally, didn't get to it yesterday

operations.push(...createRestaurantTypeOps);

// create loves relation type
const { id: relationTypeId, ops: createRelationTypeOps } = await createRelationType({

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is what a relation type is. It looks like this is mixing relations and relation types.

A relation type is something like "Works at". It's really just a Type and a Property, as we're currently using it. I think I need to update the spec to reflect this. "Relation type" should really just be a property, and not actually a type.

A relation is a thing that has a from and a to. So the fact that someone love's a restaurant is a relation. That relation would have "relation type": [Loves type/property ID].

There still is an open question on if we like this Type/Property duality or if we should go back to making something like "Loves" a separate thing, which could be called a relation type. But regardless, that doesn't seem to be what you're trying to create here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah totally, had the same question and did a call with Byron yesterday night and he clarified how it works

attributes: {
name: 'Yummy Dining',
},
mapping: restaurantMapping,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should find a way to not have to pass a mapping into every createEntity function call.

name: 'John Doe',
},
mapping: personMapping,
relations: [{ relationTypeId, toId: restaurantId }],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about having Id on the keys here. Would think that relations should each look like:
{
relationType,
to,
attributes,
relations,
}

May want to show what this looks like with more than one relation (should have separate key, vals). I assume that the from is implied here as being the entity we're creating. Think it's worth making sure we can support adding additional properties to the relations.

@nikgraf nikgraf force-pushed the ng/utility-functions branch 3 times, most recently from ab89dcc to 20ecb70 Compare February 20, 2025 16:31
@nikgraf nikgraf force-pushed the ng/utility-functions branch from 20ecb70 to 36a28d9 Compare February 21, 2025 12:18
@nikgraf nikgraf requested a review from baiirun February 21, 2025 17:07
@nikgraf nikgraf changed the title feat: add draft of first utlity functions feat: add createProperty, createType, createEntity Feb 21, 2025
const relationOp = Relation.make({
fromId: entityId,
relationTypeId: COVER_PROPERTY,
toId: cover,
Copy link
Contributor

@baiirun baiirun Feb 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cover is actually an entity id and not just a string. Not sure the best API for users creating images yet. We do have an Image.make(...) function but it requires that they've uploaded their image to IPFS already and have a URL for it.

We might need to add an Ipfs.uploadImage function. The flow then might look like this.

const cid = await Ipfs.uploadImage(imageBinary);

const { imageId, imageOps } = Image.make(cid)

Alternatively we can wrap the IPFS uploading part within Image.make

const { imageId, imageOps } = await Image.make(imageBinary)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned here #31 (comment) all IDs at the moment are strings. Is this fine or what should I change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Responded in this comment thread #31 (comment)

Copy link
Contributor

@baiirun baiirun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@baiirun baiirun merged commit 9c756b8 into main Feb 21, 2025
3 checks passed
@baiirun baiirun deleted the ng/utility-functions branch February 21, 2025 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants